<?php
/**
 * IP地址查询库
 * 
 * $Id: ip.php 125 2010-08-29 04:57:49Z Administrator $
 * $Revision: 125 $
 * $Author: Administrator $
 * $Date: 2010-08-29 12:57:49 +0800 (星期日, 29 八月 2010) $
 * @author glzaboy<glzaboy@163.com>
 * @package fastlib
 * @subpackage classes
 * @copyright 2005-2010 (c) glzaboy
 */
if (! defined ( '__FASTLIB__' )) {
	if (__DEBUG__) {
		die ( sprintf ( 'Forbidden 403 at file %s on line %d', __FILE__, __LINE__ ) );
	} else {
		die ( 'Forbidden 403' );
	}
}
/**
 * IP地址查询库
 * 
 * 本功能要求纯真IP库支持，请自行下载纯真IP对fastlib/system目录下
 * 纯真网站　http://www.cz88.net
 */
class ip {
	/**
	 * 日志记录对象
	 *
	 * @var CLASSOBJECT
	 */
	private $logObj = null;
	/**
	 * 获取IP数据索引开始位置
	 *
	 * @var int
	 */
	private $ipDataStart = null;
	/**
	 * 获取IP数据索引结束位置
	 *
	 * @var int
	 */
	private $ipDataEnd = null;
	/**
	 * IP地址总数
	 *
	 * @var int
	 */
	private $ipTotal = null;
	/**
	 * IP索引开始
	 *
	 * @var int
	 */
	private $ipBegin = null;
	/**
	 * IP索引结束
	 *
	 * @var int
	 */
	private $ipEnd = null;
	/**
	 * 程序停止错误
	 *
	 * @var bool
	 */
	private $halt = false;
	/**
	 * *$fp 文件指针
	 *
	 * @var Point
	 */
	private $fp = null;
	/**
	 * IP数据库文件初始化。
	 *
	 * @return ip
	 */
	public function __construct() {
		if (! file_exists ( __FASTLIB__ . 'vendor/qqwry.dat' )) {
			$this->putlog ( 'IP Data file ' . __FASTSYSTEM__ . 'qqwry.dat not exists.', __FILE__, __LINE__ );
			$this->halt = true;
			return null;
		}
		$this->fp = fopen ( __FASTLIB__ . 'vendor/qqwry.dat', 'rb' );
		$this->ipDataStart = fread ( $this->fp, 4 );
		$this->ipDataEnd = fread ( $this->fp, 4 );
		$this->ipBegin = implode ( '', unpack ( 'L', $this->ipDataStart ) );
		$this->ipEnd = implode ( '', unpack ( 'L', $this->ipDataEnd ) );
		if ($this->ipBegin < 0) {
			$this->ipBegin = $this->ipBegin + pow ( 2, 32 );
		}
		if ($this->ipEnd < 0) {
			$this->ipEnd = $this->ipEnd + pow ( 2, 32 );
		}
		$this->ipTotal = ($this->ipEnd - $this->ipBegin) / 7 + 1;
		return true;
	}
	/**
	 * Ip数据库信息
	 * 
	 * @return array 返回数据库信息
	 */
	public function info() {
		if ($this->halt) {
			$this->putlog ( "ip Driver has been halt.", __FILE__, __LINE__ );
			return null;
		}
		$ver = $this->findIp ( "255.255.255.0" );
		$ver = explode ( ' ', $ver );
		return array ('total' => $this->ipTotal, 'corpright' => charTounicode ( 'Data Source:' . $ver [0] . ' author :glzaboy' ), 'update' => charTounicode ( $ver [1] ) );
	}
	/**
	 * 判断是否是IP格式
	 *
	 * @param string $ipstring 查询IP字 串
	 * @return bool
	 */
	public function isIp($ipstring) {
		if (preg_match ( "/^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$/", $ipstring )) {
			return true;
		}
		return false;
	}
	/**
	 * 根据IP地址取得地理位置
	 *
	 * @param String $ip ip地址
	 * @return String
	 */
	public function findIp($ip) {
		if ($this->halt) {
			$this->putlog ( "ip Driver has been halt.", __FILE__, __LINE__ );
			return "UNKNOWN";
		}
		/**
		 * 检查IP地址合法性
		 *
		 */
		if (! $this->isIp ( $ip )) {
			return 'IP Address Error';
		}
		/**
		 * 分解IP进行运算，得出整形数
		 *
		 */
		$ipNum = sprintf ( '%u', ip2long ( $ip ) );
		/**
		 * 获取IP数据索引开始和结束位置
		 *
		 */
		$BeginNum = 0;
		$EndNum = $this->ipTotal;
		/**
		 * 使用二分查找法从索引记录中搜索匹配的IP记录
		 *
		 */
		$ip1num = $ip2num = $ipAddr1 = $ipAddr2 = null;
		while ( $ip1num > $ipNum || $ip2num < $ipNum ) {
			$Middle = intval ( ($EndNum + $BeginNum) / 2 );
			//偏移指针到索引位置读取4个字节
			fseek ( $this->fp, $this->ipBegin + 7 * $Middle );
			$ipData1 = fread ( $this->fp, 4 );
			if (strlen ( $ipData1 ) < 4) {
				//fclose($this->fp);
				return 'System Error1';
			}
			//提取出来的数据转换成长整形，如果数据是负数则加上2的32次幂
			$ip1num = implode ( '', unpack ( 'L', $ipData1 ) );
			if ($ip1num < 0)
				$ip1num += pow ( 2, 32 );
				//提取的长整型数大于我们IP地址则修改结束位置进行下一次循环
			if ($ip1num > $ipNum) {
				$EndNum = $Middle;
				continue;
			}
			//取完上一个索引后取下一个索引
			$DataSeek = fread ( $this->fp, 3 );
			if (strlen ( $DataSeek ) < 3) {
				return 'System Error2';
			}
			$DataSeek = implode ( '', unpack ( 'L', $DataSeek . chr ( 0 ) ) );
			fseek ( $this->fp, $DataSeek );
			$ipData2 = fread ( $this->fp, 4 );
			if (strlen ( $ipData2 ) < 4) {
				return 'System Error3';
			}
			$ip2num = implode ( '', unpack ( 'L', $ipData2 ) );
			if ($ip2num < 0)
				$ip2num += pow ( 2, 32 );
				//没找到提示未知
			if ($ip2num < $ipNum) {
				if ($Middle == $BeginNum) {
					return 'Unknown4';
				}
				$BeginNum = $Middle;
			}
		}
		//下面的代码读晕了，没读明白，有兴趣的慢慢读
		$ipFlag = fread ( $this->fp, 1 );
		if ($ipFlag == chr ( 1 )) {
			$ipSeek = fread ( $this->fp, 3 );
			if (strlen ( $ipSeek ) < 3) {
				return 'System Error5';
			}
			$ipSeek = implode ( '', unpack ( 'L', $ipSeek . chr ( 0 ) ) );
			fseek ( $this->fp, $ipSeek );
			$ipFlag = fread ( $this->fp, 1 );
		}
		if ($ipFlag == chr ( 2 )) {
			$AddrSeek = fread ( $this->fp, 3 );
			if (strlen ( $AddrSeek ) < 3) {
				return 'System Error6';
			}
			$ipFlag = fread ( $this->fp, 1 );
			if ($ipFlag == chr ( 2 )) {
				$AddrSeek2 = fread ( $this->fp, 3 );
				if (strlen ( $AddrSeek2 ) < 3) {
					return 'System Error7';
				}
				$AddrSeek2 = implode ( '', unpack ( 'L', $AddrSeek2 . chr ( 0 ) ) );
				fseek ( $this->fp, $AddrSeek2 );
			} else {
				fseek ( $this->fp, - 1, SEEK_CUR );
			}
			while ( ($char = fread ( $this->fp, 1 )) != chr ( 0 ) )
				$ipAddr2 .= $char;
			$AddrSeek = implode ( '', unpack ( 'L', $AddrSeek . chr ( 0 ) ) );
			fseek ( $this->fp, $AddrSeek );
			while ( ($char = fread ( $this->fp, 1 )) != chr ( 0 ) )
				$ipAddr1 .= $char;
		} else {
			fseek ( $this->fp, - 1, SEEK_CUR );
			while ( ($char = fread ( $this->fp, 1 )) != chr ( 0 ) )
				$ipAddr1 .= $char;
			$ipFlag = fread ( $this->fp, 1 );
			if ($ipFlag == chr ( 2 )) {
				$AddrSeek2 = fread ( $this->fp, 3 );
				if (strlen ( $AddrSeek2 ) < 3) {
					return 'System Error8';
				}
				$AddrSeek2 = implode ( '', unpack ( 'L', $AddrSeek2 . chr ( 0 ) ) );
				fseek ( $this->fp, $AddrSeek2 );
			} else {
				fseek ( $this->fp, - 1, SEEK_CUR );
			}
			while ( ($char = fread ( $this->fp, 1 )) != chr ( 0 ) ) {
				$ipAddr2 .= $char;
			}
		}
		//最后做相应的替换操作后返回结果
		if (preg_match ( '/http/i', $ipAddr2 )) {
			$ipAddr2 = '';
		}
		$ipaddr = "$ipAddr1 $ipAddr2";
		$ipaddr = preg_replace ( '/CZ88.NET/is', '', $ipaddr );
		$ipaddr = preg_replace ( '/^s*/is', '', $ipaddr );
		$ipaddr = preg_replace ( '/s*$/is', '', $ipaddr );
		if (preg_match ( '/http/i', $ipaddr ) || $ipaddr == '') {
			$ipaddr = 'Unknown';
		}
		global $flibcfg;
		return iconv ( 'gbk', $flibcfg ['charset'], $ipaddr );
	}
	/**
	 * 取得域名IP
	 *
	 * 注意本函数依赖于本地的DNS，因此可能返回的信息并不正确
	 *
	 * @param String $host 域名
	 * @return String IP
	 */
	public function getIpByhost($host) {
		return gethostbyname ( $host );
	}
	/**
	 * 销毁类所占用的资源
	 *
	 */
	public function __destruct() {
		if ($this->fp) {
			fclose ( $this->fp );
		}
	}
	/**
	 * 记录日志
	 *
	 * @param String $message 日志内容
	 * @param String $file 调用文件名
	 * @param Int $line 调用文件行数
	 * @return bool 是否成功写入日志
	 */
	private function putlog($message, $file, $line) {
		if (! $this->logObj) {
			global $flibcfg;
			$this->logObj = new log ( $flibcfg ['logTimeFormat'], __CLASS__ . '.' . $flibcfg ['logFileName'] );
		}
		return $this->logObj->putlog ( $message, $file, $line );
	}
	/**
	 * 返回本模块信息
	 *
	 * @return string
	 */
	public function printInfo() {
		$version = '$Revision: 125 $';
		$moudle = __CLASS__;
		$return = '';
		$return .= startTable ();
		$ip = new ip ();
		$return .= printRow ( array (array ('class' => 'h', 'content' => '<center>' . $moudle . '</center>', 'colspan' => '2' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'author' ), array ('class' => 'v', 'content' => 'glzaboy' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'version' ), array ('class' => 'v', 'content' => $version ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'ipdata' ), array ('class' => 'v', 'pre' => true, 'content' => file_exists ( __FASTLIB__ . 'vendor/qqwry.dat' ) ? var_export ( $ip->info (), true ) : 'not exists!' ) ) );
		$return .= endTable ();
		unset ( $ip );
		return $return;
	}
}